﻿namespace Blaze.Framework.Loaders
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.IO;
    using FileSystems;
    using Logging;
    using UnityEngine;

    /// <summary>
    /// 用于加载AssetBundle。
    /// </summary>
    public sealed class AssetBundleLoader : AssetLoaderBase
    {
        /// <summary>
        /// 获取或设置获取<see cref="AssetBundleManifest"/>的仓库。
        /// </summary>
        public static IAssetBundleManifestRepository ManifestRepository
        {
            get
            {
                if (mManifestRepository == null)
                    mManifestRepository = new AssetBundleManifestRepository();
                return mManifestRepository;
            }
            set
            {
                if (value == null)
                    throw new ArgumentNullException(nameof(value));
                mManifestRepository = value;
            }
        }

        /// <summary>
        /// 获取加载完成的AssetBundle。
        /// </summary>
        public AssetBundle AssetBundle
        {
            get { return (AssetBundle) Result; }
        }

        /// <summary>
        /// 获取或设置一个值，表示是否在加载的时候忽略资源清单。
        /// </summary>
        public bool IgnoreManifest { get; set; }

        /// <summary>
        /// 获取加载进度。
        /// </summary>
        public override float Progress
        {
            get
            {
                if (mDependencyLoaders == null)
                    return mCreateAssetBundleProgress;
                return (mDependencyLoadingProgress + mCreateAssetBundleProgress) / 2;
            }
        }

        /// <summary>
        /// 当加载器需要被释放时调用此方法。
        /// </summary>
        protected override void OnDispose()
        {
            if (mDependencyLoaders != null)
            {
                foreach (var dependencyLoader in mDependencyLoaders)
                    AssetLoaderPool.Put(dependencyLoader);
                mDependencyLoaders = null;
            }

            if (mRunCoroutine != null)
                CoroutineManager.Stop(mRunCoroutine);

            if (mLoadDependencyCoroutine != null)
                CoroutineManager.Stop(mLoadDependencyCoroutine);

            if (mStreamAssetBundleLoader != null)
            {
                AssetLoaderPool.Put(mStreamAssetBundleLoader);
                mStreamAssetBundleLoader = null;
            }

            base.OnDispose();
        }

        /// <summary>
        /// 当加载器启动时调用此方法。
        /// </summary>
        protected override void OnStart()
        {
            mRunCoroutine = CoroutineManager.StartNew(run());
        }

        private bool checkError()
        {
            if (Error == null)
                return false;
            IsDone = true;
            return true;
        }

        private IEnumerator loadDependenciesAsync(AssetBundleManifest manifest)
        {
            var dependencies = manifest.GetDirectDependencies(Uri);
            if (dependencies.Length <= 0)
                yield break;

            mDependencyLoaders = new List<AssetLoaderBase>(dependencies.Length);
            for (var i = 0; i < dependencies.Length; i++)
            {
                var dependency = dependencies[i];
                var dependencyLoader = AssetLoaderPool.Get<AssetBundleLoader>(dependency)
                    .SetMode(Mode)
                    .FluentStart();
                mDependencyLoaders.Add(dependencyLoader);
            }

            while (true)
            {
                updateDependencyLoadingProgress();
                if (Error != null)
                    break;
                if (mDependencyLoadingProgress >= 1)
                    break;
                yield return null;
            }
            mDependencyLoadingProgress = 1;
        }

        private void loadDependenciesSync(AssetBundleManifest manifest)
        {
            var dependencies = manifest.GetDirectDependencies(Uri);
            if (dependencies.Length == 0)
                return;
            mDependencyLoaders = new List<AssetLoaderBase>(dependencies.Length);
            for (var i = 0; i < dependencies.Length; i++)
            {
                var dependency = dependencies[i];
                var dependencyLoader = AssetLoaderPool.Get<AssetBundleLoader>(dependency)
                    .AsSync()
                    .FluentStart();
                mDependencyLoaders.Add(dependencyLoader);
            }
            mDependencyLoadingProgress = 1;
        }

        private IEnumerator run()
        {
            if (!IgnoreManifest)
            {
                var manifest = ManifestRepository.Get(Uri);
                if (manifest == null)
                {
                    Finish("can't locate manifest for " + Uri, null);
                    yield break;
                }

                if (Mode == LoadAssetMode.Sync)
                    loadDependenciesSync(manifest);
                else
                {
                    mLoadDependencyCoroutine = CoroutineManager.StartNew(loadDependenciesAsync(manifest));
                    yield return mLoadDependencyCoroutine;
                }

                if (checkError())
                {
                    Finish(Error, Result);
                    yield break;
                }
            }

            var appConfig = AppConfigManager.Get<AppConfig>();
            var path = "assetbundles/" + Uri;
            Stream stream;
            if (appConfig.debug_mode && File.Exists(PathUtility.GetPersistentDataPath(path)))
            {
                //调试模式，最优先从PersistentData目录下加载
                stream = File.Open(Uri, FileMode.Open);
            }
            else
            {
                //正常模式，直接从文件系统加载
                stream = FileSystem.Default.Open(path);
            }

            if (stream == null)
            {
                Finish($"{path} not exists.", null);
                yield break;
            }

            mStreamAssetBundleLoader = AssetLoaderPool.Get<StreamAssetBundleLoader>(Uri)
                .SetStream(stream)
                .SetMode(Mode)
                .FluentStart();
            while (!mStreamAssetBundleLoader.IsDone)
            {
                mCreateAssetBundleProgress = mStreamAssetBundleLoader.Progress * 0.5f + 0.5f;
                yield return null;
            }

            if (!mStreamAssetBundleLoader.IsOk)
            {
                Finish(mStreamAssetBundleLoader.Error, null);
                yield break;
            }

#if UNITY_EDITOR
            if (appConfig.simulate_loading_delay > 0)
                yield return YielderFactory.WaitForSeconds(appConfig.simulate_loading_delay / 1000f);
#endif

            mCreateAssetBundleProgress = 1;
            Finish(null, mStreamAssetBundleLoader.AssetBundle);
        }

        private void updateDependencyLoadingProgress()
        {
            mDependencyLoadingProgress = 0f;
            if (mDependencyLoaders == null)
                return;

            if (mDependencyLoaders.Count == 0)
            {
                mDependencyLoadingProgress = 1f;
                return;
            }

            for (var i = 0; i < mDependencyLoaders.Count; i++)
            {
                var loader = mDependencyLoaders[i];
                if (loader.Error != null)
                {
                    Error = loader.Error;
                    return;
                }
                mDependencyLoadingProgress += loader.Progress;
            }
            mDependencyLoadingProgress /= mDependencyLoaders.Count;
        }

        private static readonly Log mLog = LogManager.GetLogger(typeof(AssetBundleLoader));
        private static IAssetBundleManifestRepository mManifestRepository;
        private float mCreateAssetBundleProgress;
        private List<AssetLoaderBase> mDependencyLoaders;
        private float mDependencyLoadingProgress;
        private Coroutine mLoadDependencyCoroutine;
        private StreamAssetBundleLoader mStreamAssetBundleLoader;
        private Coroutine mRunCoroutine;
    }
}